﻿using System;
using Birkelund.QueryStrings.Tokens;
using NUnit.Framework;

namespace Birkelund.QueryStrings.Test
{
    public class ValueTypeTokenTest
    {
        #region RenderTokenValue
        [Test]
        public void RenderTokenValue_Boolean()
        {
            IQueryStringToken<bool> token = new ValueTypeToken<bool>("a");

            string expected = "True";
            Assert.AreEqual(expected, token.RenderTokenValue(true));
        }

        [Test]
        public void RenderTokenValue_Int()
        {
            IQueryStringToken<int> token = new ValueTypeToken<int>("a");

            string expected = 999.ToString();
            Assert.AreEqual(expected, token.RenderTokenValue(999));
        }

        [Test]
        public void RenderTokenValue_UInt()
        {
            IQueryStringToken<uint> token = new ValueTypeToken<uint>("a");

            string expected = 999.ToString();
            Assert.AreEqual(expected, token.RenderTokenValue(999));
        }

        [Test]
        public void RenderTokenValue_IntNegative()
        {
            IQueryStringToken<int> token = new ValueTypeToken<int>("a");

            string expected = (-999).ToString();
            Assert.AreEqual(expected, token.RenderTokenValue(-999));
        }

        [Test]
        public void RenderTokenValue_Guid()
        {
            IQueryStringToken<Guid> token = new ValueTypeToken<Guid>("a");

            string expected = "662ac7a2-e549-47da-90ef-851e4da17546";
            Assert.AreEqual(expected, token.RenderTokenValue(new Guid("662ac7a2-e549-47da-90ef-851e4da17546")));
        }

        [Test]
        public void RenderTokenValue_Double()
        {
            IQueryStringToken<double> token = new ValueTypeToken<double>("a");

            string expected = "123%2c456";
            Assert.AreEqual(expected, token.RenderTokenValue(123.456));
        }

        [Test]
        public void RenderTokenValue_TimeSpan()
        {
            IQueryStringToken<TimeSpan> token = new ValueTypeToken<TimeSpan>("a");

            string expected = "05%3a04%3a03";
            Assert.AreEqual(expected, token.RenderTokenValue(new TimeSpan(5, 4, 3)));
        }

        [Test]
        public void RenderTokenValue_Enum()
        {
            IQueryStringToken<TestEnum> token = new ValueTypeToken<TestEnum>("a");

            string expected = 2.ToString();
            Assert.AreEqual(expected, token.RenderTokenValue(TestEnum.EnumValue2));
        }

        [TestCase(EnumRenderMode.Numeric, Result = "2")]
        [TestCase(EnumRenderMode.String, Result = "EnumValue2")]
        [TestCase(EnumRenderMode.Hex, Result = "00000002")]
        public string RenderTokenValue_Enum_FormatValue(EnumRenderMode mode)
        {
            var token = new ValueTypeToken<TestEnum>("a")
                                                    {
                                                        EnumRenderMode = mode
                                                    };

            return token.RenderTokenValue(TestEnum.EnumValue2);
        }

        [Test]
        public void RenderTokenValue_EnumFlags()
        {
            IQueryStringToken<TestFlagEnum> token = new ValueTypeToken<TestFlagEnum>("a");

            string expected = 68.ToString();
            Assert.AreEqual(expected, token.RenderTokenValue(TestFlagEnum.EnumValue2 | TestFlagEnum.EnumValue6));
        }

        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void RenderTokenValue_InvalidCustomStruct()
        {
            IQueryStringToken<TestStruct> token = new ValueTypeToken<TestStruct>("a");
        }
        #endregion

        #region ReadValue
        [Test]
        public void ReadValue_Enum_InvalidValue_Empty()
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=");
            TestEnum expected = TestEnum.EnumValue4;

            IQueryStringToken<TestEnum> token = new ValueTypeToken<TestEnum>("a")
            {
                DefaultValue = TestEnum.EnumValue4,
                ExceptionNotificationBehaviour = ExceptionNotificationBehaviour.Silent
            };

            Assert.AreEqual(expected, token.ReadValue(initialUrl));
        }

        [Test]
        public void ReadValue_Enum_InvalidValue_NotEnumMember()
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=10"); //value 10 is not defined in enum
            TestEnum expected = TestEnum.EnumValue4;

            IQueryStringToken<TestEnum> token = new ValueTypeToken<TestEnum>("a")
            {
                DefaultValue = TestEnum.EnumValue4,
                ExceptionNotificationBehaviour = ExceptionNotificationBehaviour.Silent
            };
            Assert.AreEqual(expected, token.ReadValue(initialUrl));
        }

        [Test]
        public void ReadValue_Enum_WithValidation_PassValidation()
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=2");
            TestEnum expected = TestEnum.EnumValue2;
            // Only EnumValue1 or EnumValue2 are valid
            IQueryStringToken<TestEnum> token = new ValueTypeToken<TestEnum>("a")
            {
                Validator = e => e == TestEnum.EnumValue1 || e == TestEnum.EnumValue2
            };
            Assert.AreEqual(expected, token.ReadValue(initialUrl));
        }

        [Test]
        public void ReadValue_Enum_WithValidation_FailValidation()
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=3");
            TestEnum expected = TestEnum.EnumValue4; // Only EnumValue1 or EnumValue2 are valid according to the validator

            IQueryStringToken<TestEnum> token = new ValueTypeToken<TestEnum>("a")
            {
                Validator = e => e == TestEnum.EnumValue1 || e == TestEnum.EnumValue2,
                DefaultValue = TestEnum.EnumValue4,
                ExceptionNotificationBehaviour = ExceptionNotificationBehaviour.Silent
            };
            Assert.AreEqual(expected, token.ReadValue(initialUrl));
        }

        [TestCase(EnumRenderMode.Numeric, "asd", ExpectedException = typeof(TokenParserException))]
        [TestCase(EnumRenderMode.String, "EnumValue3")]
        [TestCase(EnumRenderMode.String, "qøw", ExpectedException = typeof(TokenParserException))]
        [TestCase(EnumRenderMode.Hex, "00000003")]
        [TestCase(EnumRenderMode.Hex, "qøw", ExpectedException = typeof(TokenParserException))]
        public void ReadValue_Enum_WithDifferentRenderModes(EnumRenderMode mode, string value)
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=" + value);
            TestEnum expected = TestEnum.EnumValue3;

            var token = new ValueTypeToken<TestEnum>("a")
            {
                EnumRenderMode = mode
            };
            Assert.AreEqual(expected, token.ReadValue(initialUrl));
        }

        [Test]
        public void ReadValue_Enum()
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=2");
            TestEnum expected = TestEnum.EnumValue2;

            IQueryStringToken<TestEnum> token = new ValueTypeToken<TestEnum>("a");

            Assert.AreEqual(expected, token.ReadValue(initialUrl));
        }

        [Test]
        public void ReadValue_EnumFlags()
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=68");
            TestFlagEnum expected = TestFlagEnum.EnumValue2 | TestFlagEnum.EnumValue6;
            IQueryStringToken<TestFlagEnum> token = new ValueTypeToken<TestFlagEnum>("a");
            Assert.AreEqual(expected, token.ReadValue(initialUrl));
        }

        [Test]
        public void ReadValue_UInt()
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=-10");
            uint expected = 0; // = default(uint)
            IQueryStringToken<uint> token = new ValueTypeToken<uint>("a")
            {
                ExceptionNotificationBehaviour = ExceptionNotificationBehaviour.Silent
            };

            Assert.AreEqual(expected, token.ReadValue(initialUrl));
        }

        [Test]
        public void ReadValue_Bool_True()
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=True");
            IQueryStringToken<bool> token = new ValueTypeToken<bool>("a");

            Assert.IsTrue(token.ReadValue(initialUrl));
        }

        [Test]
        public void ReadValue_Bool_False()
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=False");
            IQueryStringToken<bool> token = new ValueTypeToken<bool>("a");

            Assert.IsFalse(token.ReadValue(initialUrl));
        }

        [Test]
        public void ReadValue_Bool_CannotParse1ToTrue()
        {
            Uri initialUrl = new Uri(@"http://localhost/test/testFile.aspx?a=1");
            bool hasFailure = false;

            IQueryStringToken<bool> token = new ValueTypeToken<bool>("a");
            token.ExceptionNotificationBehaviour = ExceptionNotificationBehaviour.Silent;
            token.ReadFailureAction = delegate(FailureArgs action)
            {
                if (action.Reason == FailureReason.ParseError)
                {
                    hasFailure = true;
                }
            };

            token.ReadValue(initialUrl);
            Assert.IsTrue(hasFailure);
        }
        #endregion

        private struct TestStruct
        {
            public int Value1 { get; set; }
            public bool Value2 { get; set; }
        }
    }
}
